package pl.pw.gis.gc_sa.algo;

import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.Vector;

import pl.pw.gis.gc_sa.OnColoringFinishedListener;
import pl.pw.gis.gc_sa.data.Graph;
import pl.pw.gis.gc_sa.data.Vertex;

/**
 * Klasa abstrakcyjna po ktorej musza dziedziczyc wszystkie klasy implementujace
 * algorytmy kolorowania grafu
 * 
 * @author Maciek
 * 
 */
public abstract class GcSolver extends Thread {
    /** Kolorowany graf */
    protected Graph mGraph;
    /** Seed losowania */
    protected Random mRandom;
    /** Minimalna liczba kolorow (najlepsze rozwiazanie) */
    protected int mBestCost;
    /** Lista kolorow z minimalna ich iloscia (najlepsze rozwiazanie) */
    protected int[] mBestSolution;
    /** Czy obliczenia zakonczone */
    protected boolean mDone;

    /** Liczba iteracji w wewnetrznej petli */
    protected int mNoIter;
    /** Liczba iteracji ktore nie polepszyly znalezionego wyniku */
    protected int mNoChangeIter;
    /** Czas obliczania rozwiazania */
    protected long mComputingTime;
    /** Koszt */
    protected Cost mCost;

    /** Maksymalna liczba iteracji w wewnetrznej petli */
    protected int mNoIterMax = NO_ITER_MAX_DEF;
    /** Maksymalna liczba iteracji ktore nie wprowadzily zmiany */
    protected int mNoChangeIterMax = NO_NO_CHANGE_ITER_MAX_DEF;

    private Vector<OnColoringFinishedListener> mListeners;

    public final static int USE_NO_VERTICES_AS_ITER_MAX = -1;
    public final static int USE_NO_VERTICES_X2_AS_ITER_MAX = -2;
    public final static int USE_HALF_NO_VERTICES_AS_ITER_MAX = -3;
    public final static int USE_SQRT_NO_VERTICES_AS_ITER_MAX = -4;
    public final static int NO_ITER_MAX_DEF = USE_NO_VERTICES_AS_ITER_MAX;
    public final static int NO_NO_CHANGE_ITER_MAX_DEF = 10000;

    /**
     * Konstruktor klasy.
     * 
     * @param graph
     *            graf dla ktorego przeprowadzane jest kolorowanie
     */
    public GcSolver(Graph graph) {
        mGraph = graph;
        mBestSolution = new int[mGraph.getVerticesCount()];
        mRandom = new Random();
        mDone = false;

        mListeners = new Vector<OnColoringFinishedListener>();
    }

    /**
     * Metoda klasy Thread wywolywana przy starcie watku
     */
    public void run() {
        try {
            doRun();
        } catch (ThreadDeath e) {

        } finally {
            notifyListeners();
        }
    }

    /** Po zakonczeniu obliczen koloruje nasz graf */
    public void colorize() {
        if (!mDone) return;

        int i = 0;
        for (Vertex v : mGraph.getVertices()) {
            v.setColor(mBestSolution[i]);
            i++;
        }
    }

    /**
     * Przypisuje kazdemu wierzcholkowi inny kolor (indeks od 0)
     */
    protected void initBestSolutionLegal() {
        for (int i = 0; i < mGraph.getVerticesCount(); i++) {
            mBestSolution[i] = i;
        }
    }

    /**
     * Przypisuje kazdemu wierzcholkowi taki sam kolor (0)
     */
    protected void initBestSolutionAllSame() {
        for (int i = 0; i < mGraph.getVerticesCount(); i++) {
            mBestSolution[i] = 0;
        }
    }

    /**
     * Zmienia wartosc mNoIterMax na zalezna od liczby wierzcholkow w grafie,
     * jesli jej poprzednia wartosc jest rowna jednej z predefiniowanych stalych
     */
    protected void adjustNoIterMaxValue() {
        switch (mNoIterMax) {
        case USE_NO_VERTICES_AS_ITER_MAX:
            mNoIterMax = mGraph.getVerticesCount();
            break;
        case USE_HALF_NO_VERTICES_AS_ITER_MAX:
            mNoIterMax = mGraph.getVerticesCount() / 2;
            break;
        case USE_NO_VERTICES_X2_AS_ITER_MAX:
            mNoIterMax = mGraph.getVerticesCount() * 2;
            break;
        case USE_SQRT_NO_VERTICES_AS_ITER_MAX:
            mNoIterMax = (int) Math.sqrt(mGraph.getVerticesCount());
            break;
        }
    }

    /**
     * Zwraca liczbe roznych kolorow w tablicy tab
     * 
     */
    protected int colorCount(int[] tab) {

        Set<Integer> found = new HashSet<Integer>();
        for (int color : tab)
            found.add(color);

        return found.size();
    }

    /** Czy obliczenia zostaly zakonczone */
    public boolean isDone() {
        return mDone;
    }

    /** Zwraca czas obliczen w ms */
    public long getComputingTime() {
        return mComputingTime;
    }

    /** Zwraca wyliczona minimalna ilosc kolorow */
    public int getMinColorCount() {
        return colorCount(mBestSolution);
    }

    /**
     * Sprawdza czy rozwiazanie jest poprawne (zaden kolor ze soba nie
     * sasiaduje)
     */
    public boolean isSolutionValid() {
        return GcSolver.isSolutionValid(mGraph, mBestSolution);
    }

    /**
     * Sprawdza czy rozwiazanie jest poprawne (zaden kolor ze soba nie
     * sasiaduje)
     */
    public static boolean isSolutionValid(Graph graph, int[] solution) {
        for (int i = 0; i < graph.getVerticesCount(); i++)
            for (Vertex adj : graph.getAdjacent(i))
                if (solution[i] == solution[adj.getId()]) return false;

        return true;
    }

    /**
     * Zwraca graf
     * 
     * @return graf
     */
    public Graph getGraph() {
        return mGraph;
    }

    /**
     * Dodaje listener ktory zostanie poinformowany, kiedy obliczenia zostana
     * zakonczone
     * 
     * @param listener
     *            listener informowany o zakonczeniu obliczen
     */
    public void addListener(OnColoringFinishedListener listener) {
        mListeners.add(listener);
    }

    /**
     * Metoda powiadamia wszystkie zarejestrowane listenery o zakonczeniu
     * obliczen
     */
    private void notifyListeners() {
        for (OnColoringFinishedListener l : mListeners)
            l.onColoringFinished(this);
    }

    /**
     * Abstrakcyjna metoda wywolywana przez metode run. Klasy potomne
     * implementuja ja i w niej maja przeprowadzac wszystkie obliczenia
     */
    public abstract void doRun();

    /**
     * Metoda zwracajaca nazwe wykorzystywanej metody (algorytmu) kolorowania.
     * Implementowana przez klasy potomne, sluzy wylaczenie do wypisywania
     * infromacji o algorytmie w testach
     * 
     * @return nazwa metody
     */
    public abstract String getMethodName();

    /**
     * Metoda zwracajaca informacje o parametrach algorytmu kolorowania.
     * Implementowana przez klasy potomne, sluzy wylaczenie do wypisywania
     * infromacji o algorytmie w testach
     * 
     * @return
     */
    public abstract String getMethodParameters();

    /**
     * Metoda zwracajaca informacje o wynikach dzialania algorytmu kolorowania.
     * Implementowana przez klasy potomne, sluzy wylaczenie do wypisywania
     * infromacji o algorytmie w testach
     * 
     * @return
     */
    public abstract String getMethodOutcome();
}
